package net.villonanny.entity;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.villonanny.ConversationException;
import net.villonanny.EventLog;
import net.villonanny.Synchronizer;
import net.villonanny.TranslationException;
import net.villonanny.Translator;
import net.villonanny.Util;
import net.villonanny.type.TroopTransferType;
import net.villonanny.type.TroopType;
import net.villonanny.type.TroopTypeMap;

import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.log4j.Logger;

public class RallyPoint extends Building {
	private final static Logger log = Logger.getLogger(RallyPoint.class);
	private String sendTroopsPageUrlString;
	private TroopTypeMap troops = new TroopTypeMap();
	private Village village;
//	private static final int TYPE_HERO = 11; // travian type = 10+1
//	private static final int TYPE_CATAPULT = 8; // travian type = 7+1
	private String SEND_URL_SUFFIX = "a2b.php";

	public RallyPoint(String name, String urlString, Translator translator) {
		super(name, urlString, translator);
		this.sendTroopsPageUrlString = Util.getFullUrl(getUrlString(), SEND_URL_SUFFIX);
	}
	
	public TroopTypeMap fetchSendableTroops(Util util, boolean sharp) throws ConversationException {
		Pattern p;
		Matcher m;
		// Go to main rallypoint page
		String page = util.httpGetPage(getUrlString(), sharp);
		// Go to troop transfer page
		page = util.httpGetPage(sendTroopsPageUrlString, sharp);
		// Find available troops
		// <input class="fm" type="Text" name="t1" value="" size="2" maxlength="6"></td><td class="f8"><a href="#" onClick="document.snd.t1.value=393; return false;">(393)</a></td>
		// <input class="fm" type="Text" name="t11" value="" size="2" maxlength="6"></td><td class="f8"><a href="#" onClick="document.snd.t11.value=1; return false;">(1)</a></td></tr
		for (TroopType troopType : TroopType.values()) {
			int type = troopType.toInt() + 1; // Travian is 1-based
			p = Pattern.compile(String.format("(?s)(?i)<(?:input|a)[^>]*onClick=\"document.snd.t%s.value=(\\d+);", type));
			m = p.matcher(page);
			if (m.find()) {
				String tot = m.group(1);
				try {
					troops.put(troopType, Integer.parseInt(tot));
				} catch (NumberFormatException e) {
					throw new ConversationException("Invalid troop amount: \"" + tot + "\"");
				}
			} else {
				// Maybe there are no units of this type
				p = Pattern.compile(String.format("(?s)(?i)<input[^>]+name=\"t%s\"[^>]*>\\s*<span[^>]*>\\s*<b>\\(0\\)</b>\\s*</span>", type));
				m = p.matcher(page);
				if (m.find()) {
					troops.put(troopType, 0);
				} else if (troopType!=TroopType.HERO) {
					// Throw exception only if not checking for hero
					throw new ConversationException("Can't find troop number " + (type));
				}
			}
		}
		return troops;
	}

	public int sendTroops(Util util, String x, String y, String village, TroopTypeMap troopsToSend, TroopTransferType type, String[] item) throws ConversationException {
		return sendTroops(util, x, y, village, troopsToSend, type, item, false, false, null).secondsToArrive;
	}

	public SendTroopsInfo sendTroops(Util util, String x, String y, String village, TroopTypeMap troopsToSend, TroopTransferType type, String[] item, TroopTransferType spyType) throws ConversationException {
		return sendTroops(util, x, y, village, troopsToSend, type, item, false, false, spyType);
	}
	
	public SendTroopsInfo sendTroopsEx(Util util, String x, String y, String village, TroopTypeMap troopsToSend, TroopTransferType type, String[] item, TroopTransferType spyType) throws ConversationException {
		return sendTroops(util, x, y, village, troopsToSend, type, item, false, false, spyType);
	}

	/**
	 * 
	 * @param util
	 * @param x
	 * @param y
	 * @param troopsToSend
	 * @param type
	 * @param itemString 
	 * @param itemString2 
	 * @param testOnly
	 * @param sharp
	 * @return seconds to arrive at destination
	 * @throws ConversationException
	 */
	public SendTroopsInfo sendTroops(Util util, String x, String y, String village, TroopTypeMap troopsToSend, TroopTransferType type, String[] item, boolean testOnly, boolean quick, TroopTransferType spyType) throws ConversationException {
		Pattern p;
		Matcher m;
		String page;
		
		// Go to troop transfer page
		page = util.httpGetPage(sendTroopsPageUrlString);
		String submitUrlString = sendTroopsPageUrlString; // Same page
		List<String> postNames = new ArrayList<String>();
		List<String> postValues = new ArrayList<String>();
		// Find hidden fields
		Util.addHiddenPostFields(page, "<form[^>]+action=\"a2b.php\">", postNames, postValues);

		// Log message
		StringBuffer messageTroopSend = new StringBuffer();
		
		// Add troop amounts
		boolean spyOnly = (spyType != null) && (type != TroopTransferType.REINFORCE);
		for (TroopType troopType : TroopType.values()) {
//			int toSend = troopsToSend.get(troopType)<=troops.get(troopType)
//			? troopsToSend.get(troopType)
//					: troops.get(troopType);
			int toSend = troopsToSend.get(troopType);
			
			if (troopType != TroopType.TROOP4 && toSend > 0) {
				spyOnly = false;
			}
			if (troopType == TroopType.TROOP4 && toSend <= 0) {
				spyOnly = false;
			}

			postNames.add("t" + (troopType.toInt()+1));
			if (toSend>0) {
				postValues.add(Integer.toString(toSend));
			} else {
				postValues.add("");
			}
			// Append to log
			messageTroopSend.append( toSend );
			if (troopType != TroopType.getLastValue()) {
				messageTroopSend.append(",");
			}
		}
		
		postNames.add("c");
		postValues.add(type.getHtmlValue());
		postNames.add("x");
		postValues.add(x);
		postNames.add("y");
		postValues.add(y);
		Util.addButtonCoordinates("s1", 50, 20, postNames, postValues);
		postNames.add("dname");
		postValues.add(village);
		
		// First post
		page = util.httpPostPage(submitUrlString, postNames, postValues, quick);
		postNames.clear();
		postValues.clear();

		// Find hidden fields
		int startPos = Util.addHiddenPostFields(page, "<form[^>]*action=\"a2b.php\"[^>]*>", postNames, postValues);
		Util.addButtonCoordinates("s1", 50, 20, postNames, postValues);

		String action = "Troop movement";
		p = Pattern.compile("(?s)(?i)<h1>(.*?)</h1>");
		m = p.matcher(page);
		if (m.find()) {
			action = m.group(1);
		}

		// Time
		int secondsToArrive = 0;
		p = Pattern.compile("(?s)(?i)>\\D*? (\\d?\\d:\\d\\d:\\d\\d) \\D*?<");
		m = p.matcher(page);
		m.region(startPos, page.length());
		if (m.find()) {
			String timeString = m.group(1).trim();
			secondsToArrive=Util.timeToSeconds(timeString);
			log.debug("secondsToArrive:" + secondsToArrive);
		}
		else {
			p = Pattern.compile("(?s)(?i)There is no village at those coordinates");
			m = p.matcher(page);
			if(m.find()){
				EventLog.log("There is no village at those coordinates");
				return null;
			}
			else {
				Util.saveTestPattern("Can't find time to arrive at destination", p, page);
				throw new ConversationException("Can't find time to arrive at destination");
			}
		}

		// Log line
		if (!testOnly) {
			String coordinates = "";
			if (x!=null && y!=null && !"".equals(x+y)) {
				coordinates = String.format(" (%s|%s)", x, y);
			}
			StringBuffer message = new StringBuffer(action + coordinates + ": ");
			message.append(messageTroopSend);
			EventLog.log(message.toString());
		}
		


		
		// Arrival time
		// TODO the value found here is generally a couple of seconds earlier than the real one
		// TODO We should check for it after sending the troops, or maybe adjust it by adding the time that it takes to get
		// TODO from here to the end of this method 
		int newStartPos = m.end();
		p = Pattern.compile("(?s)(?i)<span id=\"?tp2\"?>(\\d?\\d:\\d?\\d:\\d?\\d)</span>");
		m = p.matcher(page);
		m.region(newStartPos, page.length());
		String arrivalTimeString;
		if (m.find()) {
			arrivalTimeString = m.group(1).trim();
		} else {
			throw new ConversationException("Can't find arrival time");
		}

		// If is only a test
		if (testOnly) {
			return new SendTroopsInfo(secondsToArrive);
		}

		// If spy, spy
		if (spyOnly) {
			postNames.add("spy");
			postValues.add(spyType.getHtmlValue());
		}
		
		// If catapults, add catapult target
		if (troopsToSend.get(TroopType.CATAPULT)>0) {
			String kataPattern = "(?s)(?i)<select[^>]+name=\"([^\"]*)\"[^>]*>";
			String paramName = "kata";
			String paramName2 = null;
			// Search kata parameter
			p = Pattern.compile(kataPattern);
			m = p.matcher(page);
			if (m.find()) {
				paramName = m.group(1).trim();
			} else {
				throw new ConversationException("Failed to find catapult target parameter in form (troops not sent)");
			}
			newStartPos = m.end();
			
			// Search target id
			// Normalize target item
			if (item == null || item.length == 0) {
				item = new String[] { null, null };
			} else if (item.length == 1) {
				item = new String[] { item[0], null };
			}
			if (item[0] == null) {
				EventLog.log("Catapult target not set: using random");
				try {
					item[0] = translator.getFirst("random"); // There can't be more than one translation for random, as it is used just here
				} catch (TranslationException e) {
					// Try anyway
					p = Pattern.compile("(?s)(?i)<option value=\"99\">([^<]*)</option>");
					m = p.matcher(page);
					if (m.find()) {
						item[0] = m.group(1).trim();
						log.warn("Random target keyword identified from the web page");
						log.warn("The value for key.random should be \"" + item[0] + "\" in your language configuration file");
						log.warn("Please edit your language configuration file adding key.random = " + item[0]);
					}
				}
			}
			p = Pattern.compile("(?s)(?i)<option value=\"([^\"]*)\">"+item[0]+"</option>");
			m = p.matcher(page);
			if (m.find()) {
				String targetId = m.group(1).trim();
				postNames.add(paramName);
				postValues.add(targetId);
			} else {
				throw new ConversationException("Failed to find catapult target name \"" + item[0] + "\" (troops not sent)");
			}			
			// If second target defined, look for second kata parameter
			if (item[1] != null) {
				p = Pattern.compile(kataPattern);
				m = p.matcher(page);
				m.region(newStartPos, page.length());
				if (m.find()) {
					paramName2 = m.group(1).trim();
				} else {
					log.debug("Second catapult target parameter not found (ignoring)");
				}
			}
			if (paramName2!=null) {
				p = Pattern.compile("(?s)(?i)<option value=\"([^\"]*)\">" + item[1] + "</option>");
				m = p.matcher(page);
				if (m.find()) {
					String targetId2 = m.group(1).trim();
					postNames.add(paramName2);
					postValues.add(targetId2);
				} else {
					EventLog.log("Second catapult target \"" + item[1] + "\" not found (ignoring)");
				}					
			}
		}
		
		// Prepare POST method
		PostMethod httpPost = new PostMethod(submitUrlString);
		util.addHeaders(httpPost);
		NameValuePair[] postData = new NameValuePair[postNames.size()];
		for (int i = 0; i < postData.length; i++) {
			postData[i] = new NameValuePair(postNames.get(i), postValues.get(i));
		}
		httpPost.setRequestBody(postData);
		
		

//		util.httpQuickPostPage(httpPost);
//			
//		EventLog.log("Troops sent, check rally point");
		
//		p = Pattern.compile("(?s)(?i)" + arrivalTimeString);
//		m = p.matcher(page);
//		if (!m.find()) {
//			throw new ConversationException("Can't find troops in rally point reports");
//		}
		return new SendTroopsInfo( secondsToArrive, httpPost );
	}

	public void setVillage(Village village) {
		this.village = village;
	}
	
	public Village getVillage() {
		return village;
	}
	
	public class SendTroopsInfo {
		int secondsToArrive;
		PostMethod postMethod;
		
		public SendTroopsInfo(int secondsToArrive) {
			super();
			this.secondsToArrive = secondsToArrive;
		}

		public SendTroopsInfo(PostMethod postMethod) {
			super();
			this.postMethod = postMethod;
		}

		public SendTroopsInfo(int secondsToArrive, PostMethod postMethod) {
			super();
			this.secondsToArrive = secondsToArrive;
			this.postMethod = postMethod;
		}
		
		public PostMethod getPostMethod() {
			return postMethod;
		}
		
		
		public int getSecondsToArrive() {
			return secondsToArrive;
		}
		
	}
	
}
